#pragma once
#include "common.hpp"
#include <memory>
#include "Socket.hpp"

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>


#define DEFAULT_BACKLOG 16
#define FD_SIZE_LIMIT sizeof(fd_set) * 8
#define DEFAULT_FD -1 //无效文件描述符

class select_server
{
public:
    select_server(uint16_t port)
        :_listen_socket_ptr(std::make_unique<TCP_Socket>())
        ,_port(port)
        ,_running_tag(false)
    {
        //初始化辅助数组 -- 把每一位都设为无效fd(-1)
        for(int i = 0; i < FD_SIZE_LIMIT; ++i)
        {
            _fd_array[i] = DEFAULT_FD;
        }

        _listen_socket_ptr->create_listen_socket(_port, DEFAULT_BACKLOG);
        _fd_array[0] = _listen_socket_ptr->get_socket_fd();//辅助数组的第一位(0)fd 设为监听socket
    }


    void start()
    {
        LOG(wzx::log_level::DEBUG) << "select server start...";
        _running_tag = true;
        while(_running_tag)
        {
            std::cout << "_fd_array: ";
            for(int i = 0; i < FD_SIZE_LIMIT; ++i)
            {
                if(_fd_array[i] != DEFAULT_FD)
                    std::cout << _fd_array[i] << " ";
            }
            std::cout << std::endl;

            //每次进入循环都要更新select()的监视fd表
            fd_set r_fds;
            int max_fd = DEFAULT_FD;
            //1. 清空监视fd表
            FD_ZERO(&r_fds);
            //2. 用辅助数组初始化监视fd表, 并记录最大文件描述符值
            for(int i = 0; i < FD_SIZE_LIMIT; ++i)
            {
                if(_fd_array[i] == DEFAULT_FD) //无效位
                    continue;
                
                if(_fd_array[i] > max_fd) //记录最大文件描述符值
                    max_fd = _fd_array[i];

                //需要监视的fd -- 设置进监视fd表
                FD_SET(_fd_array[i], &r_fds);
            }

            //3. 使用select()开始监视
            // int ready_fd_count = select(max_fd, &r_fds, nullptr, nullptr, nullptr/*阻塞等待事件就绪*/);
            struct timeval tv = {3, 0};
            int ready_fd_count = select(max_fd, &r_fds, nullptr, nullptr, &tv);


            if(ready_fd_count > 0) //有若干个fd的事件就绪了
            {
                LOG(wzx::log_level::INFO) << "several events are ready!!!  ready_fd_count: " << ready_fd_count;

                //交给事件派发器
                event_dispatcher(r_fds);//这里的r_fds是内核输出给用户的, 即bit位位置代表该fd上的事件是否就绪

                continue;
            }
            else if(ready_fd_count == 0) //超时了 -- 没有事件就绪 (ps: 我这里的timeout设为nullptr, 不会返回0)
            {
                LOG(wzx::log_level::INFO) << "time out!!!";
                continue;
            }
            else //ready_fd_count < 0 -- select()出错了
            {
                LOG(wzx::log_level::FATAL) << "select error: " << errno;
                break;//直接不运行了
            }

        }

        _running_tag = false;
    }


    //事件派发器 -- 我们这里只设计了读事件
    void event_dispatcher(const fd_set& r_fds)
    {
        //两种读事件就绪: 1.新连接请求;  2.IO
        for(int i = 0; i < FD_SIZE_LIMIT; ++i)
        {
            if(_fd_array[i] == DEFAULT_FD)
                continue;

            if(FD_ISSET(_fd_array[i], &r_fds)) //该fd上的事件已就绪
            {
                //1. 新连接请求
                if(_fd_array[i] == _listen_socket_ptr->get_socket_fd())
                    connection_processer();
                
                //2. IO
                else
                    IO_processer(_fd_array[i], i);

            }
        }

    }


    //连接处理器 -- accept
    void connection_processer()
    {
        //此时accept一定不会阻塞, 因为_listen_socket_fd上已经有事件就绪了
        Inet_Addr client;
        int socket_fd = _listen_socket_ptr->accept_socket(client);

        //不能直接用socket_fd进行IO, 否则又会变成阻塞IO了,
        //这里要将它托管给select -- 加入_fd_array, 后续再更新给r_fds
        for(int i = 0; i < FD_SIZE_LIMIT; ++i)
        {
            if(_fd_array[i] == DEFAULT_FD)//有空位
            {
                _fd_array[i] = socket_fd;
                LOG(wzx::log_level::DEBUG) << "socket_fd insert into _fd_array successful!!!";
                return;
            }
        }
        //辅助数组里面已经没有空位了
        LOG(wzx::log_level::ERROR) << "select server is full, Unable to establish a new IO_connection!!!";
    }


    //IO处理器
    void IO_processer(int socket_fd, int pos/*该fd在辅助数组中的位置*/)
    {
        //此时recv一定不会阻塞, 因为socket_fd上已经有事件就绪了
        std::unique_ptr<Socket> socket_ptr = std::make_unique<TCP_Socket>(socket_fd);
        std::string message;//bug -- 这里读到的不一定是完整的 -- 但是我这里就简略一下 不进行完整报文处理了(>_<)
        bool ret_recv = socket_ptr->receive(message);
        if(ret_recv == true)
        {
            std::cout << "client say:> " << message << std::endl;
        }
        else //ret_recv == false; -- 对端关闭连接了
        {
            //1. 不要让select再监视该fd
            _fd_array[pos] = DEFAULT_FD;

            //2. 关闭fd
            socket_ptr->close();
        }
    }



private:
    std::unique_ptr<Socket> _listen_socket_ptr;
    uint16_t _port;

    bool _running_tag;

    int _fd_array[FD_SIZE_LIMIT];//辅助数组 -- 记录现存的fd连接
};